/**
 * @Author: Jiang Zhongxun
 * @Date: 2025-01-10 09:27:32
 * @FilePath: \ESP32_Smart_Lock\main\main.c
 * @Description:
 * @Copyright (c) 2025 by JiangZhongxun, All Rights Reserved.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include "Utils.h"
#include "freertos/queue.h"
#include "drivers/KEYBOARD_Driver.h"
#include "drivers/MOTOR_Driver.h"
#include "drivers/AUDIO_Driver.h"
#include "drivers/LED_Driver.h"
#include "drivers/FINGERPRINT_Driver.h"
#include "drivers/BT_Driver.h"
#include "drivers/WIFI_Driver.h"
#include "drivers/PASSWORD_Driver.h"
#include "drivers/OTA_Driver.h"
#include "nvs_flash.h"

// 测试密码开门的数组，存储每次按下的按键值
static uint8_t testPasswdArray[7] = {0};
static uint8_t testPasswdCount = 0;

// 是否要录入指纹
static uint8_t is_enroll_flag = 0;
// 是否有手指放在指纹模块上面
static uint8_t is_finger_on = 0;

// 是否开始 OTA 在线升级
static uint8_t is_ota_flag = 0;

// 初始化一个保存 GPIO 中断事件的队列
static QueueHandle_t gpio_event_queue = NULL;

/**
 * @description: 响应来自 GPIO 中断的回调函数，产生的中断会触发回调函数的执行
 *               IRAM_ATT: 宏定义表示回调函数在内存的某个特定位置
 * @param {void} *arg 产生中断的 GPIO 引脚号
 * @return {*} 无返回值
 */
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
    // 保存触发中断的 GPIO 引脚号
    uint32_t gpio_num = (uint32_t)arg;
    // 避免手指放在指纹模块上的时候产生过多的中断，消抖
    // 当有手指放在指纹模块上时，要么是验证指纹，要么是录制指纹，都需要禁用所有的外部中断
    if (gpio_num == FINGER_TOUCH_INT)
    {
        gpio_isr_handler_remove(FINGER_TOUCH_INT);
        gpio_isr_handler_remove(KEYBOARD_INT);
        is_finger_on = 1;
    }
    // 将 GPIO 引脚号添加到 gpio_event_queue 队列中
    // 将 GPIO 引脚号发送到队列。需要屏蔽中断【FreeRTOS 提供的 xQueueSendFromISR 已经内部实现了屏蔽中断的机制，可以安全地在中断上下文中操作队列】
    // 【为什么需要屏蔽中断？】中断服务例程运行在一个特殊的上下文中，具有更高的优先级。在 ISR 中直接访问共享资源（如队列）可能导致数据竞争或资源损坏
    xQueueSendFromISR(gpio_event_queue, &gpio_num, NULL);
}

/**
 * @description: 录入指纹的 rtos 任务
 * @param {void} *arg 参数
 * @return {*} 无返回值
 */
static void enroll_fingerprint_task(void *arg)
{
    while (1)
    {
        // 当进入录入指纹流程时, 也就是当有连续三次按下'M'的时候
        if (is_enroll_flag)
        {
            // 请放置手指
            AUDIO_Play(80);
            // 轮询，直到有手指放在指纹模块上
            while (!is_finger_on)
            {
                DelayMs(10);
            }
            // 获取已经存储的指纹数量
            uint8_t templates_count = FINGERPRINT_GetTemplatesNumber();
            FINGERPRINT_Enroll(templates_count + 1);
            // 收尾工作
            is_enroll_flag = 0;
            is_finger_on = 0;
            FINGERPRINT_Cancel();
            // FINGERPRINT_Sleep();
            while (FINGERPRINT_Sleep() == 1)
                ;
            // 重新将 按键中断 和 指纹中断 添加到中断服务例程
            // 因为在录入指纹之前通过 gpio_isr_handler_remove 的方式移除了所有中断
            gpio_isr_handler_add(KEYBOARD_INT, gpio_isr_handler, (void *)KEYBOARD_INT);
            gpio_isr_handler_add(FINGER_TOUCH_INT, gpio_isr_handler, (void *)FINGER_TOUCH_INT);
        }
        DelayMs(200);
    }
}

/**
 * @description: 验证 / 识别 指纹的 rtos 任务
 * @param {void} *arg 参数
 * @return {*} 无返回值
 */
static void identify_fingerprint_task(void *arg)
{
    for (;;)
    {
        if (!is_enroll_flag && is_finger_on)
        {
            if (FINGERPRINT_Identify() == 0)
            {
                printf("指纹识别成功 ......\r\n");
                // 指纹验证成功
                AUDIO_Play(85);
                MOTOR_Open_Lock();
            }
            else
            {
                printf("指纹识别失败 ......\r\n");
                // 指纹验证失败
                AUDIO_Play(86);
            }
            is_finger_on = 0;
            // 指纹模块先睡眠，然后再将指纹中断添加回来
            FINGERPRINT_Cancel();
            // FINGERPRINT_Sleep();
            while (FINGERPRINT_Sleep() == 1)
                ;
            // 重新将 按键中断 和 指纹中断 添加到中断服务例程
            // 因为在录入指纹之前通过 gpio_isr_handler_remove 的方式移除了所有中断
            gpio_isr_handler_add(KEYBOARD_INT, gpio_isr_handler, (void *)KEYBOARD_INT);
            gpio_isr_handler_add(FINGER_TOUCH_INT, gpio_isr_handler, (void *)FINGER_TOUCH_INT);
        }
        DelayMs(100);
    }
}

/**
 * @description: OTA 在线升级的 rtos 任务
 * @param {void} *arg 参数
 * @return {*} 无返回值
 */
static void ota_rtos_task(void *args)
{
    while (1)
    {
        if (is_ota_flag)
        {
            is_ota_flag = 0;
            // 开始 OTA 升级
            OAT_Start();
        }
        DelayMs(5000);
    }
}

/**
 * @description: 实现中断处理事件的 rtos 任务逻辑，不断轮询中断事件队列，从中取出事件并处理
 * @param {void} *arg 产生中断的 GPIO 引脚号
 * @return {*} 无返回值
 */
static void process_isr(void *arg)
{
    uint32_t gpio_num;
    for (;;)
    {
        // 如果队列中有 GPIO 中断事件，将 GPIO 引脚号存储到 gpio_num 变量中
        if (xQueueReceive(gpio_event_queue, &gpio_num, portMAX_DELAY))
        {
            // 如果产生中断的引脚号是 GPIO_NUM_0，也就是键盘的中断引脚
            if (gpio_num == KEYBOARD_INT)
            {
                // 读取按键值
                uint8_t key_num = KEYBOARD_ReadKey();
                // 按键的水滴音效
                AUDIO_Play(11);
                // 打印按键值
                printf("press key: %d ......\r\n", key_num);
                // ##########################################################【按键点亮对应的灯珠】################################################################
                // 根据按键值点亮对应的灯珠
                if (key_num == 77) // key_num == 'M'
                {
                    LED_Light(11);
                }
                else if (key_num == 35) // key_num == '#'
                {
                    printf("Lighting LED 10 for key #\r\n");
                    LED_Light(10);
                }
                else
                {
                    LED_Light(key_num);
                }
                // ##########################################################【按键点亮对应的灯珠】################################################################

                // 存储按键到测试密码的数组
                testPasswdArray[testPasswdCount++] = key_num;

                // ###########################################################【指纹模块相关操作】#################################################################
                // 连续按下 3 次 'M'，进入录制指纹功能
                if (testPasswdCount >= 3 &&
                    (testPasswdArray[testPasswdCount - 1] == 'M') &&
                    (testPasswdArray[testPasswdCount - 2] == 'M') &&
                    (testPasswdArray[testPasswdCount - 3] == 'M'))
                {
                    testPasswdCount = 0;
                    memset(testPasswdArray, 0, sizeof(testPasswdArray));
                    is_enroll_flag = 1;
                }
                // 连续按下 3 次 '#'，开始 OTA 在线升级
                if (testPasswdCount >= 3 &&
                    (testPasswdArray[testPasswdCount - 1] == '#') &&
                    (testPasswdArray[testPasswdCount - 2] == '#') &&
                    (testPasswdArray[testPasswdCount - 3] == '#'))
                {
                    testPasswdCount = 0;
                    memset(testPasswdArray, 0, sizeof(testPasswdArray));
                    is_ota_flag = 1;
                }
                // ###########################################################【指纹模块相关操作】#################################################################

                // ######################################################【和 Flash 中存储的密码进行比对】##########################################################
                if (testPasswdCount == 7)
                {
                    // !!! 校验 Flash 中存储的密码 !!!
                    // 将按下的6个按键的值转换成 u32 数值
                    uint32_t testPasswd = testPasswdArray[0] * 100000 + testPasswdArray[1] * 10000 + testPasswdArray[2] * 1000 +
                                          testPasswdArray[3] * 100 + testPasswdArray[4] * 10 + testPasswdArray[5] * 1;
                    printf("testPasswd = %ld, testPasswd[6] = %d\r\n", testPasswd, testPasswdArray[6]);
                    // 定义 Flash 句柄
                    nvs_handle password_handle;
                    nvs_open("namespace", NVS_READWRITE, &password_handle);
                    size_t length = 7;
                    char passwdInFlash[7] = {0};
                    // 从 Flash 命名空间 namespace 中读取键为 password 的对应的值
                    nvs_get_str(password_handle, "password", passwdInFlash, &length);
                    // 密码取出来之后关闭 Flash 句柄
                    nvs_close(password_handle);
                    // 因为 testPasswdArray 中的数值是 u8 类型，passwdInFlash 中的是 char 型，所以通过 - '0' 来让数值类型一致
                    int i = 0;
                    for (i = 0; i < 6; i++)
                    {
                        // 比对输入的密码和 Flash 中的密码是否一致
                        if (testPasswdArray[i] != (passwdInFlash[i] - '0'))
                        {
                            break;
                        }
                    }
                    if (i == 6 && testPasswdArray[6] == 'M')
                    {
                        // 密码验证成功
                        printf("密码验证成功 ......\r\n");
                        AUDIO_Play(68);
                        MOTOR_Open_Lock();
                    }
                    // !!! 校验临时密码 !!!
                    else if (i < 6 && testPasswdArray[6] == 'M')
                    {
                        // 将按下的6个按键的值转换成 u32 数值
                        uint32_t pressPassword = testPasswdArray[0] * 100000 + testPasswdArray[1] * 10000 + testPasswdArray[2] * 1000 +
                                                 testPasswdArray[3] * 100 + testPasswdArray[4] * 10 + testPasswdArray[5] * 1;
                        // 检验临时密码
                        if (PASSWORD_ValidateTempPassword(pressPassword))
                        {
                            // 密码验证成功
                            printf("临时密码验证成功 ......\r\n");
                            AUDIO_Play(68);
                            MOTOR_Open_Lock();
                        }
                    }
                    else
                    {
                        // 密码验证失败
                        printf("密码验证失败 ......\r\n");
                        AUDIO_Play(69);
                    }
                    testPasswdCount = 0;
                    memset(testPasswdArray, 0, sizeof(testPasswdArray));
                }
                // ######################################################【和 Flash 中存储的密码进行比对】##########################################################
            }
        }
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

/**
 * @description: 将中断处理事件函数 process_isr 注册为一个 RTOS 任务
 * @param {*} 无参
 * @return {*} 无返回值
 */
// static void ISR_QUEUE_Init(void)
// {
//     // 创建一个队列，用来保存中断事件
//     gpio_event_queue = xQueueCreate(10, sizeof(uint32_t));
//     // 将中断处理事件函数 process_isr 注册为一个 RTOS 任务
//     xTaskCreate(process_isr, "process_isr", 2048, NULL, 10, NULL);
//     // 启用中断
//     gpio_install_isr_service(0);
//     // 为指定的 GPIO 引脚注册中断处理器，当该引脚触发中断时调用对应处理器
//     // 来自 GPIO_NUM_0 也就是 KEYBOARD_INT 的中断触发的回调函数是 gpio_isr_handler
//     // 将 KEYBOARD_INT 的中断处理逻辑代理给 gpio_isr_handler
//     // 当 KEYBOARD_INT 产生中断，将触发 gpio_isr_handler 的执行
//     gpio_isr_handler_add(KEYBOARD_INT, gpio_isr_handler, (void *)KEYBOARD_INT);
// }

// main 函数在 esp-idf 中，在编译的过程中，会将 main 函数自动包含进来，无需自己编写
// esp32 的入口函数是 app_main
// main 函数是在编译过程中插入到二进制文件中的
// app_main 函数是 FreeRTOS 的一个任务，优先级是 1
void app_main(void)
{
    // ###########################################################【所有的初始化操作】################################################################
    // 初始化触摸按键
    KEYBOARD_Init();
    // 初始化电机驱动
    MOTOR_Init();
    // 初始化语音播报
    AUDIO_Init();
    // LED 灯光初始化
    LED_Init();
    // 指纹模块初始化
    FINGERPRINT_Init();

    // 初始化 NVS Flash
    esp_err_t ret;
    // Initialize NVS.
    // 初始化 esp32 自带的 flash
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 蓝牙模块初始化
    BLUETOOTH_Init();
    // WIFI模块初始化
    WIFI_Init();

    // 初始化完成
    AUDIO_Play(105);
    // ###########################################################【所有的初始化操作】################################################################

    // ##########################################################【电机驱动与语音播报】################################################################
    // 测试打开锁并播报语音
    // MOTOR_Open_Lock();
    // 测试语音播报
    // AUDIO_Play(25);
    // ##########################################################【电机驱动与语音播报】################################################################

    // #############################################################【LED 灯珠点亮】##################################################################
    // 测试点亮灯珠
    // while (1)
    // {
    //     for (int i = 0; i < 12; i++)
    //     {
    //         LED_Light(i);
    //         DelayMs(500);
    //     }
    //     DelayMs(1000);
    // }
    // #############################################################【LED 灯珠点亮】##################################################################

    // #############################################################【测试指纹模块】##################################################################
    // 测试获取指纹模块芯片序列号
    FINGERPRINT_GetSerialNumber();
    // 测试获取指纹模块中已经存储的指纹个数
    FINGERPRINT_GetTemplatesNumber();
    // 设置安全等级为 0
    FINGERPRINT_SetSecurityLevelAsZero();
    // 测试设置取消自动注册模板和自动验证指纹
    FINGERPRINT_Cancel();
    // 测试设置指纹模块进入休眠模式
    // FINGERPRINT_Sleep();
    while (FINGERPRINT_Sleep() == 1)
        ;
    // ############################################################【测试指纹模块】##################################################################

    // #################################################################【任务】#####################################################################
    //
    // ISR_QUEUE_Init();
    // 创建一个队列，用来保存中断事件
    gpio_event_queue = xQueueCreate(10, sizeof(uint32_t));
    // 启用中断
    gpio_install_isr_service(0);
    // 为指定的 GPIO 引脚注册中断处理器，当该引脚触发中断时调用对应处理器
    // 来自 GPIO_NUM_0 也就是 KEYBOARD_INT 的中断触发的回调函数是 gpio_isr_handler
    // 将 KEYBOARD_INT 的中断处理逻辑代理给 gpio_isr_handler
    // 当 KEYBOARD_INT 产生中断，将触发 gpio_isr_handler 的执行
    gpio_isr_handler_add(KEYBOARD_INT, gpio_isr_handler, (void *)KEYBOARD_INT);
    // 将 指纹相关的中断 添加到 中断服务例程
    gpio_isr_handler_add(FINGER_TOUCH_INT, gpio_isr_handler, (void *)FINGER_TOUCH_INT);
    // 将中断处理事件函数 process_isr 注册为一个 RTOS 任务
    xTaskCreate(process_isr, "process_isr", 2048, NULL, 10, NULL);
    // 将录入指纹事件函数 enroll_fingerprint_task 注册为一个 RTOS 任务
    xTaskCreate(enroll_fingerprint_task, "enroll_finger", 2048, NULL, 10, NULL);
    // 将验证识别指纹时间函数 identify_fingerprint_task 注册为一个 RTOS 任务
    xTaskCreate(identify_fingerprint_task, "identify_fingerprint_task", 2048, NULL, 10, NULL);
    // 将 OTA 在线升级的函数 ota_rtos_task 注册为一个 RTOS 任务
    xTaskCreate(ota_rtos_task, "ota_rtos_task", 8192, NULL, 10, NULL);
    // #################################################################【任务】#####################################################################
}
